package dev.ece.suit.bloom;

import dev.ece.core.cells.model.base.FboUI;
import dev.ece.core.cells.model.base.SquareUI;
import dev.ece.core.sprite.Component;
import dev.ece.core.texture.FBO;
import dev.ece.core.texture.Texture;
import dev.ece.util.tools.Camera;
import dev.ece.util.tools.Projection;
import dev.ece.util.tools.Screen;

/**
 * @author 潜行的青衣
 * @see
 */
public class BloomHDRComponent extends Component<Float, SquareUI<Float>> {

	/**
	 * 
	 */
	private static final long serialVersionUID = -4021360846667974680L;

	private BloomHDRShader bloomHDRShader;
	
	private GaussianBlurShader gaussianBlurShader;
	
	private Screen screen;
	
	private Camera camera = new Camera();
	
	private Projection projection;
	
	private FBO hdrFBO;
	
	private FBO[] brightFBO = new FBO[2];
	
	private Texture bright;
	
	private float gamma = 1.0f;
	
	private float exposure = 1.0f;
	
	private boolean enable;
	/**
	 * 高斯模糊次数
	 */
	private int count = 10;
	
	public BloomHDRComponent(BloomHDRShader bloomHDRShader, int width, int height) throws Exception {
		this(new FboUI(width, height), bloomHDRShader, FBO.newHDRInstance(width, height));
	}
	
	/**
	 * @param model
	 * @throws Exception 
	 */
	public BloomHDRComponent(SquareUI<Float> model, BloomHDRShader bloomHDRShader, FBO hdrFBO) throws Exception {
		super(model);
		this.bloomHDRShader = bloomHDRShader;
		this.hdrFBO = hdrFBO;
		this.brightFBO[0] = FBO.newHDRInstance(hdrFBO.getWidth(), hdrFBO.getHeight());
		this.brightFBO[1] = FBO.newHDRInstance(hdrFBO.getWidth(), hdrFBO.getHeight());
		this.gaussianBlurShader = GaussianBlurShader.createGaussianBlurShader();
		this.screen = new Screen(hdrFBO.getWidth(), hdrFBO.getHeight());
		this.camera.setPosition(0, 0, 2f);
		this.projection = new Projection().set2DScreen(screen);
		this.initialize();
	}

	public void begin() {
		hdrFBO.begin();
	}
	
	/**
	 * 高斯模糊
	 */
	public void renderGaussianBlur() {
		this.screen.glViewPort();
		this.projection.glProjection2D();
		this.camera.lookAt();
		
		int sourceIndex = 0;
		brightFBO[sourceIndex].begin();
		gaussianBlurShader.glUseProgram();
		gaussianBlurShader.glModelViewProjectMatrix();
		gaussianBlurShader.glStatus(2);
		gaussianBlurShader.glTexture(hdrFBO.getTexture());
		super.onRender(camera);
		
		int targetIndex = 1;
		int status = 0;
		for(int i=0; i<this.getCount(); i++) {
			sourceIndex = sourceIndex % 2;
			targetIndex = (sourceIndex + 1) % 2;
			brightFBO[targetIndex].begin();
			gaussianBlurShader.glStatus(status % 2);
			gaussianBlurShader.glTexture(brightFBO[sourceIndex].getTexture());
			super.onRender(camera);
			sourceIndex ++;
			status ++;
		}
		bright = brightFBO[targetIndex].getTexture();
	}
	
	@Override
	public void onRender(Camera camera) {
		this.bloomHDRShader.glUseProgram();
		this.bloomHDRShader.glModelViewProjectMatrix();
		this.bloomHDRShader.glEnable(isEnable());
		this.bloomHDRShader.glTexture(hdrFBO.getTexture());
		this.bloomHDRShader.glGamma(gamma);
		this.bloomHDRShader.glExposure(exposure);
		this.bloomHDRShader.glBright(bright);
		super.onRender(camera);
	}
	
	public float getGamma() {
		return gamma;
	}

	public void setGamma(float gamma) {
		this.gamma = gamma;
	}

	public float getExposure() {
		return exposure;
	}

	public void setExposure(float exposure) {
		this.exposure = exposure;
	}

	public boolean isEnable() {
		return enable;
	}

	public void setEnable(boolean enable) {
		this.enable = enable;
	}

	public int getCount() {
		return count;
	}

	public void setCount(int count) {
		this.count = count;
	}

	@Override
	public float getWidth() {
		return this.getUi().getWidth();
	}

	@Override
	public float getHeight() {
		return this.getUi().getHeight();
	}

	@Override
	public void destroy() {
		super.destroy();
		hdrFBO.destroy();
		hdrFBO = null;
		for(FBO fbo:brightFBO) {
			fbo.destroy();
		}
		brightFBO = null;
	}

}
